package com.ruoyi.framework.sharding;

import com.ruoyi.common.utils.StringUtils;
import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
//import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.rule.ShardingRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.sql.*;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: 分片算法工具类
 */
@Component
//@Order(1)
public class ShardingAlgorithmTool implements InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(ShardingAlgorithmTool.class);

    /** 表分片符号，例：mall_order_2024 中，分片符号为 "_" */
    private static final String TABLE_SPLIT_SYMBOL = "_";

    @Value("${spring.dynamic.datasource.druid.master.url}")
    private String MASTER_URL;

    @Value("${spring.dynamic.datasource.druid.master.username}")
    private String MASTER_USERNAME;

    @Value("${spring.dynamic.datasource.druid.master.password}")
    private String MASTER_PASSWORD;

    @Resource
    private ShardingSphereDataSource shardingSphereDataSource;

    private static String DATASOURCE_URL;
    private static String DATASOURCE_USERNAME;
    private static String DATASOURCE_PASSWORD;

    @Override
    public void afterPropertiesSet() throws Exception {
        DATASOURCE_URL = MASTER_URL;
        DATASOURCE_USERNAME = MASTER_USERNAME;
        DATASOURCE_PASSWORD = MASTER_PASSWORD;
        tableNameCacheReloadAll(shardingSphereDataSource);
    }


    /**
     * 获取ContextManager
     * @param dataSource
     * @return
     */
    public static ContextManager getContextManager(final ShardingSphereDataSource dataSource) {
        try {
            Field field = ShardingSphereDataSource.class.getDeclaredField("contextManager");
            field.setAccessible(true);
            return (ContextManager) field.get(dataSource);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 重载全部缓存
     * @param shardingSphereDataSource
     */
    public static void tableNameCacheReloadAll(ShardingSphereDataSource shardingSphereDataSource) {
        Arrays.stream(ShardingTableCache.values()).forEach(t -> ShardingAlgorithmTool.tableNameCacheReload(t, shardingSphereDataSource));
    }

    /**
     * 重载指定分表缓存
     * @param logicTable 逻辑表，例：mall_order
     * @param shardingSphereDataSource
     */
    public static void tableNameCacheReload(ShardingTableCache logicTable, ShardingSphereDataSource shardingSphereDataSource) {
        // 读取数据库中所有表名
        List<String> tableNameList = getAllTableNameBySchema(logicTable.logicTableName());
        // 更新缓存、配置（原子操作）
        logicTable.atomicUpdateCacheAndActualDataNodes(tableNameList, shardingSphereDataSource);
        // 删除旧的缓存（如果存在）
        logicTable.resultTableNamesCache().clear();
        // 写入新的缓存
        logicTable.resultTableNamesCache().addAll(tableNameList);
        // 动态更新配置 actualDataNodes
//        actualDataNodesRefresh(logicTable.logicTableName(), tableNameList);
    }

    /**
     * 获取所有表名
     * @return 表名集合
     * @param logicTableName 逻辑表
     */
    public static List<String> getAllTableNameBySchema(String logicTableName) {
        List<String> tableNames = new ArrayList<>();
        if (StringUtils.isEmpty(DATASOURCE_URL) || StringUtils.isEmpty(DATASOURCE_USERNAME) || StringUtils.isEmpty(DATASOURCE_PASSWORD)) {
            log.error("数据库连接配置有误，请稍后重试，URL:{}, username:{}, password:{}", DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
            throw new IllegalArgumentException("数据库连接配置有误，请稍后重试");
        }
        try (Connection conn = DriverManager.getConnection(DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
             Statement st = conn.createStatement()) {
            try (ResultSet rs = st.executeQuery("show TABLES like '" + logicTableName + TABLE_SPLIT_SYMBOL + "%'")) {
                tableNames.add(logicTableName);
                while (rs.next()) {
                    String tableName = rs.getString(1);
                    // 匹配分表格式 例：^(mall\_contract_\d{6})$
                    if (tableName != null && tableName.matches(String.format("^(%s\\d{6})$", logicTableName + TABLE_SPLIT_SYMBOL))) {
                        tableNames.add(rs.getString(1));
                    }
                }
            }
        } catch (SQLException e) {
            log.error("数据库连接失败，请稍后重试，原因：{}", e.getMessage(), e);
            throw new IllegalArgumentException("数据库连接失败，请稍后重试");
        }
        return tableNames;
    }

    /**
     * 动态更新配置 actualDataNodes
     *  @param logicTableName  逻辑表名
     * @param tableNamesCache 真实表名集合
     * @param shardingSphereDataSource
     */
    public static void actualDataNodesRefresh(String logicTableName, List<String> tableNamesCache, ShardingSphereDataSource shardingSphereDataSource)  {
        try {
            // 获取数据分片节点
            String dbName = "sharding";
            log.info("更新分表配置，logicTableName:{}，tableNamesCache:{}", logicTableName, tableNamesCache);

            // generate actualDataNodes
            String newActualDataNodes = tableNamesCache.stream().map(o -> String.format("%s.%s", dbName, o)).collect(Collectors.joining(","));
//            ShardingSphereDataSource shardingSphereDataSource = (ShardingSphereDataSource) YamlShardingSphereDataSourceFactory.createDataSource(ShardingConfig.getShardingYAMLFile());
            updateShardRuleActualDataNodes(shardingSphereDataSource, logicTableName, newActualDataNodes);
        }catch (Exception e){
            log.error("初始化动态表单失败，原因：{}", e.getMessage(), e);
        }
    }

    /**
     * 刷新ActualDataNodes
     */
    private static void updateShardRuleActualDataNodes(ShardingSphereDataSource dataSource, String logicTableName, String newActualDataNodes) {
        // Context manager.
        ContextManager contextManager = getContextManager(dataSource);

        // Rule configuration.
        String schemaName = "chmk";
        Collection<RuleConfiguration> newRuleConfigList = new LinkedList<>();
//        Collection<RuleConfiguration> oldRuleConfigList = contextManager.getMetaDataContexts()
//                .getMetaData()
//                .getGlobalRuleMetaData()
//                .getConfigurations();

//        RuleConfiguration oldRuleConfig = contextManager.getMetaDataContexts()
//                .getMetaData().getDatabase(schemaName).getRuleMetaData().getSingleRule(ShardingRule.class).getConfiguration();
        Collection<RuleConfiguration> oldRuleConfigList = contextManager.getMetaDataContexts()
                .getMetaData(schemaName)
                .getRuleMetaData()
                .getConfigurations();
        for (RuleConfiguration oldRuleConfig : oldRuleConfigList) {
            if (oldRuleConfig instanceof ShardingRuleConfiguration) {

                // Algorithm provided sharding rule configuration
                ShardingRuleConfiguration oldAlgorithmConfig = (ShardingRuleConfiguration) oldRuleConfig;
                ShardingRuleConfiguration newAlgorithmConfig = new ShardingRuleConfiguration();

                // Sharding table rule configuration Collection
                Collection<ShardingTableRuleConfiguration> newTableRuleConfigList = new LinkedList<>();
                Collection<ShardingTableRuleConfiguration> oldTableRuleConfigList = oldAlgorithmConfig.getTables();

                oldTableRuleConfigList.forEach(oldTableRuleConfig -> {
                    if (logicTableName.equals(oldTableRuleConfig.getLogicTable())) {
                        ShardingTableRuleConfiguration newTableRuleConfig = new ShardingTableRuleConfiguration(oldTableRuleConfig.getLogicTable(), newActualDataNodes);
                        newTableRuleConfig.setTableShardingStrategy(oldTableRuleConfig.getTableShardingStrategy());
                        newTableRuleConfig.setDatabaseShardingStrategy(oldTableRuleConfig.getDatabaseShardingStrategy());
                        newTableRuleConfig.setKeyGenerateStrategy(oldTableRuleConfig.getKeyGenerateStrategy());

                        newTableRuleConfigList.add(newTableRuleConfig);
                    } else {
                        newTableRuleConfigList.add(oldTableRuleConfig);
                    }
                });

                newAlgorithmConfig.setTables(newTableRuleConfigList);
                newAlgorithmConfig.setAutoTables(oldAlgorithmConfig.getAutoTables());
                newAlgorithmConfig.setBindingTableGroups(oldAlgorithmConfig.getBindingTableGroups());
                newAlgorithmConfig.setDefaultDatabaseShardingStrategy(oldAlgorithmConfig.getDefaultDatabaseShardingStrategy());
                newAlgorithmConfig.setDefaultTableShardingStrategy(oldAlgorithmConfig.getDefaultTableShardingStrategy());
                newAlgorithmConfig.setDefaultKeyGenerateStrategy(oldAlgorithmConfig.getDefaultKeyGenerateStrategy());
                newAlgorithmConfig.setDefaultShardingColumn(oldAlgorithmConfig.getDefaultShardingColumn());
                newAlgorithmConfig.setShardingAlgorithms(oldAlgorithmConfig.getShardingAlgorithms());
                newAlgorithmConfig.setKeyGenerators(oldAlgorithmConfig.getKeyGenerators());

                newRuleConfigList.add(newAlgorithmConfig);
            }
        }

        // update context
        contextManager.alterRuleConfiguration(schemaName, newRuleConfigList);
    }

    /**
     * 创建分表
     * @param logicTableName 逻辑表
     * @param resultTableName 真实表名，例：mall_order_2024
     * @return 创建结果（true创建成功，false未创建）
     */
    public static boolean createShardingTable(String logicTableName, String resultTableName) {
        // 根据日期判断，当前年份之后分表不提前创建
//        String yearMonth = resultTableName.replace(logicTableName + TABLE_SPLIT_SYMBOL,"");
//        YearMonth shardingMonth = YearMonth.parse(yearMonth, DateTimeFormatter.ofPattern("yyyyMM"));
//        if (shardingMonth.isAfter(YearMonth.now())) {
//            return false;
//        }
        synchronized (logicTableName.intern()) {
            // 缓存中无此表，则建表并添加缓存
            executeSql(Collections.singletonList("CREATE TABLE IF NOT EXISTS `" + resultTableName + "` LIKE `" + logicTableName + "`;"));
        }
        return true;
    }

    /**
     * 执行SQL
     * @param sqlList SQL集合
     */
    private static void executeSql(List<String> sqlList) {
        if (StringUtils.isEmpty(DATASOURCE_URL) || StringUtils.isEmpty(DATASOURCE_USERNAME) || StringUtils.isEmpty(DATASOURCE_PASSWORD)) {
            log.error("数据库连接配置有误，请稍后重试，URL:{}, username:{}, password:{}", DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
            throw new IllegalArgumentException("数据库连接配置有误，请稍后重试");
        }
        try (Connection conn = DriverManager.getConnection(DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD)) {
            try (Statement st = conn.createStatement()) {
                conn.setAutoCommit(false);
                for (String sql : sqlList) {
                    st.execute(sql);
                }
            } catch (Exception e) {
                conn.rollback();
                log.error("数据表创建执行失败，请稍后重试，原因：{}", e.getMessage(), e);
                throw new IllegalArgumentException("数据表创建执行失败，请稍后重试");
            }
        } catch (SQLException e) {
            log.error("数据库连接失败，请稍后重试，原因：{}", e.getMessage(), e);
            throw new IllegalArgumentException("数据库连接失败，请稍后重试");
        }
    }
}
